﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace XTYAPI.PLC
{
    public class PIDWD
    {
        #region 字段
        /// <summary>
        /// 积分值，用于计算输出量  
        /// </summary>
        private double integral;
        /// <summary>
        /// 微分值，用于计算输出量  
        /// </summary>
        private double derivative;
        #endregion
        #region 属性
        #region 设定
        /// <summary>
        /// 设定比例系数，用于控制输出量与误差的比例关系  
        /// </summary>
        public double Kp { get; set; } = 10;
        /// <summary>
        /// 设定积分系数，用于控制输出量与误差的积分关系
        /// </summary>
        public double Ki { get; set; } = 20;
        /// <summary>
        /// 设定微分系数，用于控制输出量与误差的微分关系  
        /// </summary>
        private double Kd { get; set; } = 0;
        /// <summary>
        /// 设定补偿值,用于最小输出
        /// </summary>
        public double Compensate { get; set; } = 0;
        /// <summary>
        /// 控制周期
        /// </summary>
        public double Kt { get; set; } = 1.0;
        /// <summary>
        /// 只做微分正补偿
        /// </summary>
        public bool D_Positively { get; set; }
        #endregion
        #region 输出值
        /// <summary>
        /// 偏差值
        /// </summary>
        public double Error { get;private set; }
        /// <summary>
        /// 比例输出值
        /// </summary>
        public double P_Out { get; private set; }
        /// <summary>
        /// 积分值输出
        /// </summary>
        public double I_Out { get;  private set; }
        /// <summary>
        /// 微分值输出
        /// </summary>
        public double D_Out { get;  private set; }
        /// <summary>
        /// 总输出
        /// </summary>
        public double Sum_out { get; private set; }       
        /// <summary>
        /// 积分时间
        /// </summary>
        public double I_time { get; private set; } = 1;
        /// <summary>
        /// 上一次误差值，用于计算微分值  
        /// </summary>
        public double PreviousError { get; private set; }
        /// <summary>
        /// 上次目标
        /// </summary>
        public double LastSet { get; set; }
        #endregion
        #endregion
        #region 方法
        /// <summary>
        /// 构造函数，用于初始化PID控制器对象，并设置比例、积分和微分系数
        /// </summary>
        /// <param name="kp"></param>
        /// <param name="ki"></param>
        /// <param name="kd"></param>
        public PIDWD(double kp, double ki, double kd,double kt=1):this()
        {
            this.Kp = kp;
            this.Ki = ki;
            this.Kd = kd;
            this.Kt = kt;
        }
        public PIDWD()
        {
            this.PreviousError = 0;  // 将上一次误差值初始化为0  
            this.integral = 0;  // 将积分值初始化为0  
            this.derivative = 0;  // 将微分值初始化为0  
        }
        /// <summary>
        /// PID计算,若取消积分作用积分可以设置为0
        /// </summary>
        /// <param name="setpoint">设定值</param>
        /// <param name="actualValue">当前值</param>
        /// <param name="count">间隔时间</param>
        /// <returns></returns>
        public double CalPID(double setpoint, double actualValue)
        {         
            Error = setpoint - actualValue;// 计算误差，即设定值与实际值的差值  
            #region 比例计算
            P_Out = Kp* Error;
            #endregion
            #region 积分计算
            bool change = false;
            if (setpoint!= LastSet)//设定改变积分重置检测
            {
                change = true;
                LastSet = setpoint;
            }
            if (change||integral > int.MaxValue-930805)//设定改变或积分饱和检测
            {
                integral = 0;
                I_time = 1;
            }
            integral=Error* Kt+ integral;//时间积分
            I_Out = Ki * integral / I_time; //平均 + 放大
            I_time++;//时间自增
            #endregion
            #region 微分计算
            if (change)//设定改变微分参考值清0
            {
                PreviousError = 0;
            }     
            derivative = Error - PreviousError; // 计算微分值，即误差的变化率  这次偏差-上次偏差
            var d_value= Kd * derivative/Kt;//微分变化率结果 
            if (D_Positively&& d_value <0)//是否屏蔽抑制作用， 用于只应对意外
            {
                D_Out = 0;
            }
            else //平常心对待
            {
                D_Out = d_value;
            }                     
            PreviousError = Error; // 将当前误差值存储在previousError属性中，以便下一次计算使用  
            #endregion
            #region 比例+积分+微分+补偿
            Sum_out = P_Out + I_Out + D_Out + Compensate;                
            return Sum_out;
            #endregion
        }
        #endregion
    }
}
